package com.own.component.elastic.util;

import co.elastic.clients.elasticsearch._types.mapping.Property;
import com.own.component.elastic.constant.ElasticSearchCommonConstant;
import com.own.component.elastic.core.base.aop.ElasticFieldCommon;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * ElasticMappingResolveUtil
 *
 * @author chenxueli
 * @date 2022-09-03 22:52:00
 */
@Slf4j
public final class ElasticMappingResolveUtil {

    private static final Set<Class<?>> IGNORE_CLAZZ = Set.of(
            Map.class,
            Collection.class,
            List.class,
            ArrayList.class,
            Set.class
    );

    private static final Set<Class<?>> COLLECTION_CLAZZ = Set.of(
            List.class,
            ArrayList.class,
            Set.class
    );

    /**
     * 获取索引映射
     *
     * @param clazz es po类
     * @return 索引映射
     */
    public static Map<String, Property> resolve(Class<?> clazz) {
        return resolve(clazz, new HashMap<>(16));
    }

    /**
     * 获取索引映射
     *
     * @param clazz    es po类
     * @param mappings 索引映射
     * @return 索引映射
     */
    public static Map<String, Property> resolve(Class<?> clazz, Map<String, Property> mappings) {
        var fields = clazz.getDeclaredFields();
        // 处理字段列表
        resolve(fields, mappings);
        // 如果还有父级元素，还需要继续执行
        var superClass = clazz.getSuperclass();
        if (superClass != null) {
            resolve(superClass, mappings);
        }
        return mappings;
    }

    /**
     * 获取索引映射
     *
     * @param fields   字段数组
     * @param mappings 索引映射
     */
    private static void resolve(Field[] fields, Map<String, Property> mappings) {
        for (var field : fields) {
            resolve(field, mappings);
        }
    }

    /**
     * 获取最终的字段类型
     *
     * @param field 字段
     * @return 类型
     */
    private static Class<?> getResultFieldType(Field field) {
        if (COLLECTION_CLAZZ.contains(field.getType())) {
            var listType = (ParameterizedType) field.getGenericType();
            return (Class<?>) listType.getActualTypeArguments()[0];
        }
        return field.getType();
    }

    /**
     * 获取索引映射
     *
     * @param field   字段
     * @param mapping 索引映射
     */
    private static void resolve(Field field, Map<String, Property> mapping) {
        var type = getResultFieldType(field);
        // 如果字段在忽略的类型中，直接不处理
        if (IGNORE_CLAZZ.contains(type)) {
            return;
        }
        // 使用字段上高级注解去解析，如果解析成功了直接返回
        if (resolveForHandlerTypeMap(type, field, mapping)) {
            return;
        }
        // 使用字段上的通用注解去解析，如果解析成功了直接返回
        if (resolveForHandlerTypeCommon(type, field, mapping)) {
            return;
        }
        // 使用字段的类型去解析
        if (resolveForFieldType(type, field, mapping)) {
            return;
        }
        // 设置默认的字段处理
        mapping.put(field.getName(), Property.of(ElasticSearchCommonConstant.DEFAULT_HANDLER));
    }

    /**
     * 使用字段上高级注解去解析
     *
     * @param type    字段类型
     * @param field   字段
     * @param mapping 映射
     * @return 是否存在有效的解析了
     */
    private static boolean resolveForHandlerTypeMap(Class<?> type, Field field, Map<String, Property> mapping) {
        // 查询所有可执行的内容
        ElasticFieldHandlerRegisterUtil.HANDLER_TYPE_MAP.forEach((key, value) -> {
            // 判断是否在注解中
            var annotation = field.getAnnotation(key);
            if (annotation != null) {
                var types = ElasticFieldHandlerRegisterUtil.HANDLER_TYPE_TYPE_MAP.get(key);
                // 类型判断的时需要将忽略的类型去掉
                if (types != null && !types.contains(type)) {
                    return;
                }
                // 执行函数
                addMappings(key, annotation, field, mapping);
            }
        });
        return mapping.containsKey(field.getName());
    }

    /**
     * 使用字段上的通用注解去解析
     *
     * @param type    字段类型
     * @param field   字段
     * @param mapping 映射
     * @return 是否存在有效的解析了
     */
    private static boolean resolveForHandlerTypeCommon(Class<?> type, Field field, Map<String, Property> mapping) {
        // 获取该字段上的ElasticFieldCommon注解
        var common = field.getAnnotation(ElasticFieldCommon.class);
        if (common == null) {
            return false;
        }
        for (var key : common.handlerKeys()) {
            if (resolveForKey(key, type, field, mapping)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 使用字段的类型去解析
     *
     * @param type    字段类型
     * @param field   字段
     * @param mapping 映射
     * @return 是否存在有效的解析了
     */
    private static boolean resolveForFieldType(Class<?> type, Field field, Map<String, Property> mapping) {
        // 去查询当前类型的解析方式
        var key = ElasticFieldHandlerRegisterUtil.HANDLER_TYPE_KEY_MAP.get(type);
        // 通过key去寻找对应的处理方式
        return resolveForKey(key, type, field, mapping);
    }

    /**
     * 使用key去解析
     *
     * @param key     字段key
     * @param type    字段类型
     * @param field   字段
     * @param mapping 映射
     * @return 是否存在有效的解析了
     */
    private static boolean resolveForKey(String key, Class<?> type, Field field, Map<String, Property> mapping) {
        // 根据字符串去查找对应的处理器
        var handler = ElasticFieldHandlerRegisterUtil.HANDLER_KEY_MAP.get(key);
        if (handler != null) {
            var types = ElasticFieldHandlerRegisterUtil.HANDLER_KEY_TYPE_MAP.get(key);
            if (types != null && !types.contains(type)) {
                return false;
            }
            // 执行函数
            addMappings(handler.annotation(), null, field, mapping);
            return true;
        }
        return false;
    }

    /**
     * 添加索引映射
     *
     * @param key        类型
     * @param annotation 注解
     * @param field      字段
     * @param mappings   索引映射
     */
    private static void addMappings(
            Class<? extends Annotation> key,
            Annotation annotation,
            Field field,
            Map<String, Property> mappings
    ) {
        var handler = ElasticFieldHandlerRegisterUtil.HANDLER_TYPE_MAP.get(key);
        if (handler == null) {
            return;
        }
        var property = handler.handle(annotation, field);
        if (property != null) {
            mappings.put(field.getName(), property);
        }
    }

}
